//
//  HUMOpenGLShader.swift
//  OpenGLDemo1
//
//  Created by Ming on 2018/9/30.
//  Copyright © 2018年 Ming. All rights reserved.
//

import UIKit

class HUMOpenGLShader {
    private enum ShaderType {
        case vertex, fragment
    }
    
    private var programId: GLuint = 0
    
    deinit {
        glDeleteProgram(programId)
    }
    
    init?(vertexShaderSource: String, fragmentShaderSource: String) {
        do {
            let vertexShaderId = try compileShader(source: vertexShaderSource, type: .vertex)
            let fragmentShaderId = try compileShader(source: fragmentShaderSource, type: .fragment)
            try linkProgram(vertexShaderId: vertexShaderId, fragmentShaderId: fragmentShaderId)
            glDeleteShader(vertexShaderId)
            glDeleteShader(fragmentShaderId)
        } catch let error {
            print("ming: 初始化shader失败: \(error)")
            return nil
        }
    }
    
    convenience init?(vertextShaderPath: String?, fragmentShaderPath: String?) {
        guard let vertextShaderPath = vertextShaderPath, let fragmentShaderPath = fragmentShaderPath else {
            return nil
        }
        do {
            let vertextSource = try String(contentsOfFile: vertextShaderPath)
            let fragmentSource = try String(contentsOfFile: fragmentShaderPath)
            self.init(vertexShaderSource: vertextSource, fragmentShaderSource: fragmentSource)
        } catch let error {
            print("ming: 从文件读取shader初始化shader失败: \(error)")
            return nil
        }
    }
}

// MARK: Public
extension HUMOpenGLShader {
    func use() {
        glUseProgram(programId)
    }
}

// MARK: Private
extension HUMOpenGLShader {
    private func compileShader(source: String, type: ShaderType) throws -> GLuint {
        var shaderId: GLuint = 0
        guard let cString = source.cString(using: .utf8) else {
            let description = "Shader source transform to cString failed"
            throw NSError(domain: "com.ming.opengles", code: -1000, userInfo: [NSLocalizedDescriptionKey: description])
        }
        var sourcePointer: UnsafePointer<GLchar>? = UnsafePointer<GLchar>(cString)
        switch type {
        case .vertex:   shaderId = glCreateShader(GLenum(GL_VERTEX_SHADER))
        case .fragment: shaderId = glCreateShader(GLenum(GL_FRAGMENT_SHADER))
        }
        glShaderSource(shaderId, 1, &sourcePointer, nil)
        glCompileShader(shaderId)
        
        var compileStatus: GLint = 0
        glGetShaderiv(shaderId, GLenum(GL_COMPILE_STATUS), &compileStatus)
        if compileStatus == 0 {
            var description = ""
            var logLength: GLint = 0
            glGetShaderiv(shaderId, GLenum(GL_INFO_LOG_LENGTH), &logLength)
            if logLength > 0 {
                var compileLog = [CChar](repeating: 0, count: Int(logLength))
                glGetShaderInfoLog(shaderId, logLength, &logLength, &compileLog)
                description = String(format: "Shader compile error log: %@", String(cString: compileLog))
            }
            throw NSError(domain: "com.ming.opengles", code: -1000, userInfo: [NSLocalizedDescriptionKey: description])
        }
        return shaderId
    }
    private func linkProgram(vertexShaderId: GLuint, fragmentShaderId: GLuint) throws {
        programId = glCreateProgram()
        glAttachShader(programId, vertexShaderId)
        glAttachShader(programId, fragmentShaderId)
        glLinkProgram(programId)
        
        var linkStatus: GLint = 0
        glGetProgramiv(programId, GLenum(GL_LINK_STATUS), &linkStatus)
        if linkStatus == 0 {
            var description = ""
            var logLength: GLint = 0
            glGetProgramiv(programId, GLenum(GL_INFO_LOG_LENGTH), &logLength)
            if logLength > 0 {
                var linkLog = [CChar](repeating: 0, count: Int(logLength))
                glGetProgramInfoLog(programId, logLength, &logLength, &linkLog)
                description = String(format: "Shader program link error log: %@", String(cString: linkLog))
            }
            throw NSError(domain: "com.ming.opengles", code: -1000, userInfo: [NSLocalizedDescriptionKey: description])
        }
    }
}
